﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.VisualStudio.Debugger.Interop;
using Red.CommUtils.Application;
using Red.Core.Network;
using Red.Core.Network.PacketNetwork;
using Red.Scripts.Debugger.Defines;

namespace Red.Scripts.Debugger.Remote
{
    public class Port : IDebugPort2, IDebugDefaultPort2, IDebugPortNotify2, IConnectionPointContainer
    {
        #region Private Data

        private IDebugPortSupplier2 _portSupplier;
        private IDebugPortRequest2 _request;
        private Uri _uri;
        private readonly Guid _guid = Guid.NewGuid();

        private PacketNetworkManager _network;
        HashSet<IDebugProgramNode2> _nodes = new HashSet<IDebugProgramNode2>();

        #endregion Private Data

        #region Public Properties

        public Guid Guid => _guid;
        public IDebugCoreServer2 Server { get; set; }

        #endregion Public Properties

        #region C-Tor

        public Port( IDebugPortSupplier2 portSupplier, IDebugPortRequest2 request, Uri uri )
        {
            _portSupplier = portSupplier;
            _request = request;
            _uri = uri;
        }

        #endregion C-Tor

        #region Private Methods

        private void InitializeNetwork()
        {
            if( _network == null )
                _network = new PacketNetworkManager( new RawTcpManager( ErrorHandler ) );
        }

        #endregion Private Methods

        #region Event Handlers

        private void ErrorHandler( Exception ex )
        {
            Debug.Print( $"Connection Error: {ex}" );
        }

        #endregion Event Handlers

        #region IDebugPort2

        int IDebugPort2.EnumProcesses( out IEnumDebugProcesses2 ppEnum )
        {
            EnumeratedProcesses processes = new EnumeratedProcesses();

            InitializeNetwork();
            ChannelFactory factory = new ChannelFactory( _network );

            IEnumerable<UriProcess> foundProcesses = UriProcess.PollForProcesses( this, factory, _uri );
            processes.Elements.AddRange( foundProcesses );

            ppEnum = processes;
            return Constants.S_OK;
        }

        int IDebugPort2.GetPortId( out Guid pguidPort )
        {
            pguidPort = _guid;

            return Constants.S_OK;
        }

        int IDebugPort2.GetPortName( out string pbstrName )
        {
            pbstrName = _uri.ToString();

            return Constants.S_OK;
        }

        int IDebugPort2.GetPortRequest( out IDebugPortRequest2 ppRequest )
        {
            ppRequest = _request;

            return Constants.S_OK;
        }

        int IDebugPort2.GetPortSupplier( out IDebugPortSupplier2 ppSupplier )
        {
            ppSupplier = _portSupplier;

            return Constants.S_OK;
        }

        int IDebugPort2.GetProcess( AD_PROCESS_ID ProcessId, out IDebugProcess2 ppProcess )
        {
            InitializeNetwork();
            ChannelFactory factory = new ChannelFactory( _network );

            UriBuilder uriBuilder = new UriBuilder( _uri );
            uriBuilder.Port = UriProcess.DEFAULT_PORT;

            ppProcess = new UriProcess( uriBuilder.Uri, this, factory, (int)ProcessId.dwProcessId );

            return Constants.S_OK;
        }

        #endregion IDebugPort2

        #region IDebugDefaultPort2

        int IDebugDefaultPort2.EnumProcesses( out IEnumDebugProcesses2 ppEnum )
        {
            return ( (IDebugPort2)this ).EnumProcesses( out ppEnum );
        }

        int IDebugDefaultPort2.GetPortId( out Guid pguidPort )
        {
            return ( (IDebugPort2)this ).GetPortId( out pguidPort );
        }

        int IDebugDefaultPort2.GetPortName(out string pbstrName )
        {
            return ( (IDebugPort2)this ).GetPortName( out pbstrName );
        }

        int IDebugDefaultPort2.GetPortRequest( out IDebugPortRequest2 ppRequest )
        {
            return ( (IDebugPort2)this ).GetPortRequest( out ppRequest );
        }

        int IDebugDefaultPort2.GetPortSupplier( out IDebugPortSupplier2 ppSupplier )
        {
            return ( (IDebugPort2)this ).GetPortSupplier( out ppSupplier );
        }

        int IDebugDefaultPort2.GetProcess( AD_PROCESS_ID ProcessId, out IDebugProcess2 ppProcess )
        {
            return ( (IDebugPort2)this ).GetProcess( ProcessId, out ppProcess );
        }

        int IDebugDefaultPort2.GetPortNotify( out IDebugPortNotify2 ppPortNotify )
        {
            ppPortNotify = this;

            return Constants.S_OK;
        }

        int IDebugDefaultPort2.GetServer( out IDebugCoreServer3 ppServer )
        {
            throw new NotImplementedException();
        }

        int IDebugDefaultPort2.QueryIsLocal()
        {
            throw new NotImplementedException();
        }

        #endregion IDebugDefaultPort2

        #region IDebugPortNotify2

        private class ProgramCreated : IDebugEvent2, IDebugProgramCreateEvent2
        {
            #region Event

            public static Guid IID = new Guid( "96CD11EE-ECD4-4E89-957E-B5D496FC4139" );

            int IDebugEvent2.GetAttributes( out uint pdwAttrib )
            {
                pdwAttrib = (uint)enum_EVENTATTRIBUTES.EVENT_SYNCHRONOUS;

                return Constants.S_OK;
            }

            #endregion Event
        }
        private class ProgramDestroyed : IDebugEvent2, IDebugProgramDestroyEvent2
        {
            #region Event

            public static Guid IID = new Guid( "E147E9E3-6440-4073-A7B7-A65592C714B5" );

            int IDebugEvent2.GetAttributes( out uint pdwAttrib )
            {
                pdwAttrib = (uint)enum_EVENTATTRIBUTES.EVENT_SYNCHRONOUS;

                return Constants.S_OK;
            }

            int IDebugProgramDestroyEvent2.GetExitCode( out uint pdwExit )
            {
                pdwExit = 0;

                return Constants.S_OK;
            }

            #endregion Event
        }

        int IDebugPortNotify2.AddProgramNode( IDebugProgramNode2 pProgramNode )
        {
            if ( _nodes.Add( pProgramNode ) )
            {
                Debug.Assert( _connectionPoint != null );

                Program program = pProgramNode as Program;

                if( program != null )
                {
                    _connectionPoint.Event( this, program.ParentProcess, program, new ProgramCreated(), ProgramCreated.IID );
                }

                return Constants.S_OK;
            }

            return Constants.S_FALSE;
        }

        int IDebugPortNotify2.RemoveProgramNode( IDebugProgramNode2 pProgramNode )
        {
            if ( _nodes.Remove( pProgramNode ) )
            {
                Debug.Assert( _connectionPoint != null );

                Program program = pProgramNode as Program;

                if ( program != null )
                {
                    _connectionPoint.Event( this, program.ParentProcess, program, new ProgramDestroyed(), ProgramDestroyed.IID );
                }

                return Constants.S_OK;
            }

            return Constants.S_FALSE;
        }

        #endregion IDebugPortNotify2

        #region IConnectionPointContainer

        ConnectionPoint _connectionPoint;

        void IConnectionPointContainer.EnumConnectionPoints( out IEnumConnectionPoints ppEnum )
        {
            throw new NotImplementedException();
        }

        void IConnectionPointContainer.FindConnectionPoint( ref Guid riid, out IConnectionPoint ppCP )
        {
            if ( _connectionPoint == null )
                _connectionPoint = new ConnectionPoint( this, riid, Server );

            Debug.Assert( riid == _connectionPoint.Guid );

            ppCP = _connectionPoint;
        }

        #endregion IConnectionPointContainer
    }

    public class ConnectionPoint : IConnectionPoint
    {
        #region Public Properties

        public IConnectionPointContainer Parent { get; private set; }
        public Guid Guid { get; private set; }
        public IDebugCoreServer2 Server { get; private set; }

        #endregion Public Properties

        #region Private Data

        private int _idPool = 0;
        private IDictionary<int,IDebugPortEvents2> _sinks = new Dictionary<int, IDebugPortEvents2>();

        #endregion Private Data

        #region C-Tor

        public ConnectionPoint( IConnectionPointContainer parent, Guid guid, IDebugCoreServer2 server )
        {
            Parent = parent;
            Guid = guid;
            Server = server;
        }

        #endregion C-Tor

        #region Public Methods

        public void Event( IDebugPort2 pPort, IDebugProcess2 pProcess, IDebugProgram2 pProgram, IDebugEvent2 pEvent, Guid riidEvent )
        {
            foreach ( var pair in _sinks )
            {
                IDebugPortEvents2 postman = pair.Value;

                int result = postman.Event( Server, pPort, pProcess, pProgram, pEvent, ref riidEvent );

                Debug.Print( $"Send result for sink {pair.Key}: {result}" );
            }
        }

        #endregion Public Methods

        #region IConnectionPoint

        void IConnectionPoint.Advise( object pUnkSink, out int pdwCookie )
        {
            IDebugPortEvents2 sink = pUnkSink as IDebugPortEvents2;

            _sinks.Add( ++_idPool, sink );

            pdwCookie = _idPool;
        }

        void IConnectionPoint.EnumConnections( out IEnumConnections ppEnum )
        {
            throw new NotImplementedException();
        }

        void IConnectionPoint.GetConnectionInterface( out Guid pIID )
        {
            pIID = Guid;
        }

        void IConnectionPoint.GetConnectionPointContainer( out IConnectionPointContainer ppCPC )
        {
            ppCPC = Parent;
        }

        void IConnectionPoint.Unadvise( int dwCookie )
        {
            _sinks.Remove( dwCookie );
        }

        #endregion IConnectionPoint
    }
}

